package edu.cornell.lassp.houle.RngPack;

//
// RngPack 1.1a by Paul Houle
// http://www.honeylocust.com/RngPack/
//

/**
 * RandomElement is an abstract class that encapsulates random number generators. To base a class on
 * it, you must define the method <CODE>raw()</CODE> as described below. It is also likely that
 * you will want to define a constructor or another mechanism for seeding the the generator. The
 * other classes defined in <CODE>RandomElement</CODE> add value to the numbers generated by
 * <CODE>raw()</CODE>
 * 
 * <P>
 * <A HREF="/RngPack/src/edu/cornell/lassp/houle/RngPack/RandomElement.java"> Source code </A> is
 * available.
 * 
 * @author <A HREF="http://www.honeylocust.com/"> Paul Houle </A> (E-mail: <A
 *         HREF="mailto:paul@honeylocust.com">paul@honeylocust.com</A>)
 * @version 1.1a
 * 
 * @see RandomJava
 * @see RandomShuffle
 */

public abstract class RandomElement
    extends Object implements Cloneable {

  Double BMoutput; // constant needed by Box-Mueller algorithm

  /**
   * The abstract method that must be defined to make a working RandomElement. See the class <CODE>RandomJava</CODE>
   * for an example of how to do this.
   * 
   * @see RandomJava
   * 
   * @return a random double in the range [0,1]
   */

  abstract public double raw();

  /**
   * Fill part or all of an array with doubles. The method defined here uses multiple calls to
   * <CODE>raw()</CODE> to fill the array. You can eliminate the overhead of multiple method calls
   * by subclassing this with a version of the generator that fills the array. On our system this
   * improves the efficiency of <CODE>Ranecu</CODE> by 20% when filling large arrays.
   * 
   * 
   * @param d array to be filled with doubles
   * @param n number of doubles to generate
   */

  public void raw(double d[], int n) {
    for (int i = 0; i < n; i++)
      d[i] = raw();
  }

  /**
   * Fill an entire array with doubles. This method calls <CODE>raw(double d[],int n)</CODE> with
   * <CODE>d=d.length</CODE>. Since this adds little overhead for <CODE>d.length</CODE> large,
   * it is only necessary to override <CODE>raw(double d[],int n)</CODE>
   * 
   * @param d array to be filled with doubles.
   */

  public void raw(double d[]) {
    raw(d, d.length);
  }

  /**
   * @param hi upper limit of range
   * @return a random integer in the range 1,2,... ,<STRONG>hi</STRONG>
   */

  public int choose(int hi) {
    return choose(1, hi);
  }

  /**
   * @param lo lower limit of range
   * @param hi upper limit of range
   * @return a random integer in the range <STRONG>lo</STRONG>, <STRONG>lo</STRONG>+1, ... ,<STRONG>hi</STRONG>
   */

  public int choose(int lo, int hi) {
    int value = lo + (int) ((hi - lo) * raw());
    if (value > hi)
      value = hi; /*
                   * otherwise a generator on [0,1] could return a result outside of the legal
                   * range: you can override this if you know a generator returns [0,1) or (0,1).
                   */
    return value;
  }

  /**
   * @return a boolean that's true 0.5 of the time; equivalent to coin(0.5).
   */

  public boolean coin() {
    return raw() <= 0.5;
  }

  /**
   * @param p probability that function will return true
   * @return a boolean that's true p of the time.
   */

  public boolean coin(double p) {
    return raw() <= p;
  }

  /**
   * @param lo lower limit of range
   * @param hi upper limit of range
   * @return a uniform random real in the range [<STRONG>lo</STRONG>,<STRONG>hi</STRONG>]
   */
  public double uniform(double lo, double hi) {
    return (lo + (hi - lo) * raw());
  }

  /**
   * gaussian() uses the Box-Muller algorithm to transform raw()'s into gaussian deviates.
   * 
   * @return a random real with a gaussian distribution, standard deviation
   * 
   */

  public double gaussian() {
    double out, x, y, r, z;

    if (BMoutput != null) {
      out = BMoutput.doubleValue();
      BMoutput = null;
      return (out);
    }
    ;

    do {
      x = uniform(-1, 1);
      y = uniform(-1, 1);
      r = x * x + y * y;
    }
    while (r >= 1.0);

    z = Math.sqrt(-2.0 * Math.log(r) / r);
    BMoutput = new Double(x * z);
    return (y * z);
  }

  /**
   * 
   * @param sd standard deviation
   * @return a gaussian distributed random real with standard deviation <STRONG>sd</STRONG>
   */

  public double gaussian(double sd) {
    return (gaussian() * sd);
  }

  /**
   * 
   * generate a power-law distribution with exponent <CODE>alpha</CODE> and lower cutoff <CODE>cut</CODE>
   * <CENTER> </CENTER>
   * 
   * @param alpha the exponent
   * @param cut the lower cutoff
   * 
   */

  public double powlaw(double alpha, double cut) {
    return cut * Math.pow(raw(), 1.0 / (alpha + 1.0));
  }

  public Object clone() throws java.lang.CloneNotSupportedException {
    return super.clone();
  }

};
